//	 Copyright 2011 Elian ORIOU
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package org.eo.geomapper.db;

import java.awt.Color;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.regex.Pattern;

import org.eo.geomapper.objects.GeoLine;
import org.eo.geomapper.objects.GeoPoint;
import org.eo.geomapper.objects.GeoPolygon;
import org.eo.geomapper.objects.ILayer;
import org.eo.geomapper.objects.ObjectType;
import org.eo.geomapper.objects.SimpleLayer;

/**
 * Recovers layer content from a GIS database
 * 
 * @author Elian ORIOU
 * 
 */

public class GeoMapperDBImporter {

	private Connection dbConnection;

	private static final String SignedNumberPattern = "[+-]?\\d+\\.?\\d*";
	private static final String pointPattern = SignedNumberPattern + "\\s"
			+ SignedNumberPattern;
	private static final String multiPointsPattern = "\\((" + pointPattern
			+ "[,]?)+\\)";
	private static final String lineStringPattern = "LINESTRING"
			+ multiPointsPattern;
	private static final String polygonPattern = "POLYGON\\(("
			+ multiPointsPattern + "[,]?)+\\)";

	public GeoMapperDBImporter() {
	}

	/**
	 * Connects to GIS database using connection informations provided by user
	 * 
	 * @param host
	 *            The DB host
	 * @param username
	 *            The DB username
	 * @param password
	 *            The DB password
	 */

	public void connect(String host, String username, String password) {
		Properties props = new Properties();
		props.setProperty("user", username);
		props.setProperty("password", password);
		try {
			Class.forName("org.postgresql.Driver").newInstance();
			dbConnection = DriverManager.getConnection(host, props);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Imports and builds a layer from the GIS database
	 * 
	 * @param type
	 *            The Layer type
	 * @param tableName
	 *            The table which contains informations
	 * @param nameField
	 *            The field which contains object names
	 * @param coordField
	 *            The field which contains object coordinates
	 * @return the built layer
	 */

	public ILayer importLayer(ObjectType type, String tableName,
			String nameField, String coordField) {
		try {
			Statement st = dbConnection.createStatement();
			ResultSet result;
			switch (type) {
			case POINT:
				result = st.executeQuery("SELECT " + nameField + ", X("
						+ coordField + ")" + ", Y(" + coordField + ") FROM "
						+ tableName + ";");
				ILayer pointLayer = new SimpleLayer(tableName, type,
						Color.BLACK);
				pointLayer.setColor(Color.BLACK);
				while (result.next()) {
					pointLayer.addObject(new GeoPoint(result.getString(1),
							result.getInt(2), result.getInt(3)));
				}
				return pointLayer;
			case LINE:
				result = st.executeQuery("SELECT " + nameField + ", asText("
						+ coordField + ") FROM " + tableName + ";");
				ILayer lineLayer = new SimpleLayer(tableName, type, Color.BLACK);
				lineLayer.setColor(Color.BLACK);
				while (result.next()) {
					GeoLine line = extractGeoLine(result.getString(2));
					if (line != null) {
						line.setName(result.getString(1));
						lineLayer.addObject(line);
					}
				}
				return lineLayer;
			case POLYGON:
				result = st.executeQuery("SELECT " + nameField + ", asText("
						+ coordField + ") FROM " + tableName + ";");
				ILayer polygonLayer = new SimpleLayer(tableName, type,
						Color.BLACK);
				polygonLayer.setColor(Color.BLACK);
				while (result.next()) {
					GeoPolygon polygon = extractGeoPolygon(result.getString(2));
					if (polygon != null) {
						polygon.setName(result.getString(1));
						polygonLayer.addObject(polygon);
					}
				}
				return polygonLayer;
			}
			st.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Parses and builds a line object from its WKB representation (for instance
	 * "LINESTRING(0 0,1 1,1 2)")
	 * 
	 * @param stringRepresentation
	 *            The WKB representation
	 * @return
	 */

	private GeoLine extractGeoLine(String stringRepresentation) {
		if (!Pattern.matches(lineStringPattern, stringRepresentation)) {
			return null;
		}
		GeoLine line = new GeoLine("");
		int startIndex = stringRepresentation.indexOf('(');
		String points = stringRepresentation.substring(startIndex + 1,
				stringRepresentation.length() - 1);
		for (int i = 0; i < points.split(",").length; i++) {
			String point = points.split(",")[i];
			int x = Integer.parseInt(point.split("\\s")[0]);
			int y = Integer.parseInt(point.split("\\s")[1]);
			line.addPoint(new GeoPoint(x, y));
		}
		return line;
	}

	/**
	 * Parses and builds a polygon object from its WKB representation (for
	 * instance "POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1))")
	 * 
	 * @param stringRepresentation
	 *            The WKB representation
	 * @return
	 */

	private GeoPolygon extractGeoPolygon(String stringRepresentation) {
		if (!Pattern.matches(polygonPattern, stringRepresentation)) {
			return null;
		}
		GeoPolygon polygon = new GeoPolygon("");
		int firstIndex = stringRepresentation.indexOf('(');
		int startIndex = stringRepresentation.indexOf('(', firstIndex + 1);
		String points = stringRepresentation.substring(startIndex,
				stringRepresentation.length() - 1);
		String[] elements = points.split("\\)[,]\\(");
		for (int i = 0; i < elements.length; i++) {
			elements[i] = elements[i].replaceAll("[\\(\\)]", "");
			String[] subPoints = elements[i].split(",");
			for (int j = 0; j < subPoints.length; j++) {
				int x = Integer.parseInt(subPoints[j].split("\\s")[0]);
				int y = Integer.parseInt(subPoints[j].split("\\s")[1]);
				polygon.addPoint(new GeoPoint(x, y));
			}
		}
		return polygon;
	}
}